package com.ybear.ybhotrepair;

import android.content.Context;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.ybear.ybhotrepair.utils.DigestUtil;
import com.ybear.ybhotrepair.utils.FieldUtils;
import com.ybear.ybhotrepair.utils.LogUtil;

import java.io.File;
import java.lang.ref.SoftReference;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;

/**
 * 类文件热修复 - 内部调用
 */
public class ClassHotRepair {
    private static final String DEX_SUFFIX = ".dex";
    private static final String FOLDER_HOT_REPAIR = "yb_hot_repair";
    private static final String FOLDER_DEX = "dex";
    private static final String FOLDER_LOCAL_DEX = "local_dex";
    private static final String FIELD_DEX_ELEMENTS = "dexElements";

    public static final int LOAD_REPAIR_DEX_SUCCESS = 0;
    public static final int LOAD_REPAIR_DEX_FAILURE = -1;
    public static final int LOAD_REPAIR_DEX_NOT_FILE = -2;
    public static final int LOAD_REPAIR_DEX_NOT_PATH = -3;

    private Context mContext;
    private Thread mClearThread;
    private List<SoftReference<File>> clearDexQueue = new ArrayList<>();

    public ClassHotRepair(@NonNull Context context) {
        mContext = context;
    }

    /**
     * 获取默认dex补丁文件目录
     * @return          返回：{@link #getDefaultDexDir()} local_dex
     */
    @NonNull
    public File getLocalDexPatchDirFile() {
        File f = new File(getDefaultDexDir() + FOLDER_LOCAL_DEX + File.separator);
        if( !f.exists() ) {
            if( f.mkdirs() ) LogUtil.d("DexPatchFileDir_mkdirs：" + f.getAbsolutePath());
        }else {
            LogUtil.d("DexPatchFileDir：" + f.getAbsolutePath());
        }
        return f;
    }

    /**
     * 加载修复的dex文件
     * @param dexFile       dex文件路径（可以存在多个dex文件）
     * @return              修复结果
     */
    public int loadRepairDexFile(@Nullable List<File> dexFile) {
        if( dexFile == null || dexFile.size() == 0 ) {
            LogUtil.e("Repair list of dex file does not exist.");
            return LOAD_REPAIR_DEX_NOT_FILE;
        }

        StringBuilder paths = new StringBuilder();
        for( File file : dexFile ) {
            if( file.getName().endsWith( DEX_SUFFIX ) ) {
                paths.append(file.getAbsolutePath()).append(":");
            }
        }
        LogUtil.d("loadRepairDexFile:" + paths.toString());
        if( paths.length() == 0 ) return LOAD_REPAIR_DEX_NOT_PATH;
        return injectDex( paths.substring(0, paths.length() - 1) ) ?
                LOAD_REPAIR_DEX_SUCCESS : LOAD_REPAIR_DEX_FAILURE;
    }
    public int loadRepairDexFile(@Nullable File... dexFile) {
        if( dexFile == null ) return LOAD_REPAIR_DEX_NOT_FILE;
        return loadRepairDexFile( Arrays.asList( dexFile ) );
    }
    public int loadRepairDexFile(@NonNull String... hash) {
        return loadRepairDexFile( getCheckDexFile( hash ) );
    }

    /**
     * 加载修复Dex文件目录
     * @param dexFileDir    加载所有dex文件的目录
     * @return              修复结果
     */
    public int loadRepairDexFileDir(@NonNull File dexFileDir) {
        return loadRepairDexFile(dexFileDir.listFiles());
    }
    public int loadRepairDexFileDir() {
        return loadRepairDexFileDir(getLocalDexPatchDirFile());
    }

    /**
     * 获取检查通过的Dex文件
     * @param hash    校验值（sha256）
     * @return        Dex文件
     */
    @Nullable
    public List<File> getCheckDexFile(@NonNull String... hash) {
        File[] fs = getLocalDexPatchDirFile().listFiles();
        Map<String, File> callFiles = new HashMap<>();
        if( fs == null || hash.length > fs.length ) {
            LogUtil.d(String.format("Failed to get check by dex file:{hash:%s, fs:%s}",
                    hash.length, fs == null ? null : fs.length));
            return null;
        }

        for( File f : fs ) {
            String fsha = DigestUtil.toFileSHA256( f );
            for ( String sha : hash ) {
                if( fsha.equals( sha ) ) {
                    if( callFiles.containsKey( sha ) ) {
                        //重复的Dex文件加入删除队列
                        addClearDexToQueue( f );
                        LogUtil.d(String.format(
                                "Add the repeat dex file:%s to delete a queue.", f.getName())
                        );
                    }else {
                        callFiles.put( sha, f );
                    }
                }
            }
            if( !callFiles.containsKey( fsha ) ) {
                //验证失败的Dex文件加入删除队列
                addClearDexToQueue( f );
                LogUtil.d(String.format(
                        "Add the validation failure dex file:%s to delete a queue.", f.getName())
                );
            }
        }
        clearQueueOfDex();
        return new ArrayList<>(callFiles.values());
    }

    public int getDexFileCount() {
        File[] fs = getLocalDexPatchDirFile().listFiles();
        return fs == null ? 0 : fs.length;
    }

    /**
     * 清除所有dex文件
     */
    public synchronized void clearDexFileAll() {
        if( mClearThread != null ) return;
        File[] fs = getLocalDexPatchDirFile().listFiles();
        if( fs == null ) return;
        mClearThread = new Thread(() -> {
            for( File f : fs ) addClearDexToQueue( f );
            mClearThread = null;
            clearQueueOfDex();
        });
        mClearThread.start();

    }

    /**
     * 清除队列中的dex文件
     */
    private synchronized void clearQueueOfDex() {
        if( mClearThread != null && clearDexQueue.size() != 0 ) return;
        mClearThread = new Thread(() -> {
            for( SoftReference<File> srFile : clearDexQueue ) {
                File f = srFile.get();
                if( f != null ) {
                    boolean isDel = f.delete();
                    LogUtil.d(String.format(
                            "This Dex file:%s delete result:%s", f.getName(), isDel)
                    );
                }
                srFile.clear();
            }
            clearDexQueue.clear();
            mClearThread = null;
        });
        mClearThread.start();
    }

    /**
     * 添加清除的dex文件到队列
     * @param f     文件
     */
    private void addClearDexToQueue(File f) {
        clearDexQueue.add( new SoftReference<>( f ) );
    }

    /**
     * 注入dex
     * @param loaderDex     需要注入的所有dex文件路径
     *                      例如：xxx/xxx/classes1.dex:xxx/xxx/classes2.dex
     * @return              注入结果
     */
    private boolean injectDex(@NonNull String loaderDex) {
        //自定义优化目录
        String optimizeDir = getDefaultOptimizeDir();
        PathClassLoader pathLoader = (PathClassLoader) mContext.getClassLoader();
        //通过Dex加载器去加载修复的dex文件
        DexClassLoader dexLoader = new DexClassLoader(
                loaderDex, optimizeDir, null, pathLoader
        );
        /* 获取BaseDexClassLoader内部的DexPathList */
        Object dexList = getPathListField( dexLoader );
        Object pathList = getPathListField( pathLoader );
        /* 获取DexPathList内部的DexElements */
        Object dexElements = getDexElementsField( dexList );
        Object pathElements = getDexElementsField( pathList );
        //数组合并
        Object newDexElements = arraycopy(dexElements, pathElements);
        //重新获取一次DexPathList
        pathList = getPathListField( pathLoader );

        if( pathList == null ) return false;
        //重新赋值
        return FieldUtils.setField(
                pathList, pathList.getClass(), FIELD_DEX_ELEMENTS, newDexElements
        );
    }

    /**
     * 数组复制
     * @param src       源数组
     * @param dest      目标数组
     * @return          复制结果
     */
    private Object arraycopy(@Nullable Object src, @Nullable Object dest) {
        if( src == null || dest == null ) return null;
        //获取源目标的原始类型
        Class<?> srcComType = src.getClass().getComponentType();
        int srcLen = Array.getLength( src );
        int destLen = Array.getLength( dest );
        //实例一个总长度大小的源目标
        Object newSrc = Array.newInstance(srcComType, srcLen + destLen);
        System.arraycopy(src, 0, newSrc, 0, srcLen);
        System.arraycopy(dest, 0, newSrc, srcLen, destLen);
        return newSrc;
    }

    /**
     * 获取默认目录。
     * @return          返回：data/data/包名/files/热修复文件夹/dex文件夹/
     */
    private String getDefaultDexDir() {
        return mContext.getFilesDir()
                .getAbsoluteFile() + File.separator +
                FOLDER_HOT_REPAIR + File.separator +
                FOLDER_DEX + File.separator;
    }

    /**
     * 获取默认的优化目录
     * @return          返回：{@link #getDefaultDexDir()} optimize_dex
     */
    private String getDefaultOptimizeDir() {
        File f = new File(getDefaultDexDir() + "optimize_dex");
        if( !f.exists() ) {
            if( f.mkdirs() ) LogUtil.d("OptimizeDir_mkdirs：" + f.getAbsolutePath());
        }else {
            LogUtil.d("OptimizeDir：" + f.getAbsolutePath());
        }
        return f.getAbsolutePath();
    }

    /**
     * 获取 dexElements 属性成员
     * @param obj   待获取的对象
     * @return      属性成员
     */
    @Nullable
    private Object getDexElementsField(@Nullable Object obj) {
        return obj == null ? null : FieldUtils.getField(obj, obj.getClass(), FIELD_DEX_ELEMENTS);
    }

    /**
     * 获取类加载器下的 pathList 属性成员
     * @param clsLoader     类加载器
     * @return              属性成员
     */
    @Nullable
    private Object getPathListField(@Nullable ClassLoader clsLoader) {
        try {
            final Class<?> dexCls = Class.forName("dalvik.system.BaseDexClassLoader");
            return FieldUtils.getField(clsLoader, dexCls, "pathList");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}